/*
Copyright (C) 2022-2024 Inspur Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package controller

import (
	"did-service/auth"
	"did-service/bean/entity"
	"did-service/bean/param"
	"did-service/common/constant"
	"did-service/common/crypto/gm"
	"did-service/common/http"
	"encoding/json"
	"fmt"
	"time"

	"github.com/emicklei/go-restful"
	"github.com/pkg/errors"
)

// CredentialGrantCreate 创建授权
func (dc *DidController) CredentialGrantCreate(req *restful.Request, resp *restful.Response) {
	var (
		grantParam param.CredentialGrant
		walletId   = auth.GetCurrentWalletId(req)
	)
	if err := req.ReadEntity(&grantParam); err != nil {
		commonHttpErr(
			fmt.Sprintf("parameter types do not match error: %s", err.Error()),
			"参数格式不正确", resp)
		return
	}
	grantParam.CreateDate = time.Now().Format(constant.TimeLayout)

	// 校验参数是否合法
	if !dc.grantParamValid(grantParam) {
		commonHttpErr("", "参数不合法", resp)
		return
	}

	// 根据凭证id获取owner钱包和owner did
	ownerWallet, ownerDid := dc.srv.GetCredentialOwnerInfo(grantParam.ObjectId)
	if ownerWallet == "" || ownerDid == "" {
		commonHttpErr("", "没有找到凭证所有者信息", resp)
		return
	}
	// 如果不属于同一个钱包
	if ownerWallet != walletId {
		commonHttpErr(fmt.Sprintf("own wallet %s not same with %s", walletId, ownerWallet),
			"凭证所有者不一致", resp)
		return
	}
	grantParam.Owner = ownerDid

	// 是自己无需添加授权
	if grantParam.Owner == grantParam.Grantee {
		commonHttpErr("", "无需授权自己", resp)
		return
	}

	var (
		grantId string
	)
	// 校验授权是否已经存在，没有过期且没有被撤销
	grantCheckData, err := dc.srv.GetCredentialGrantCreate(grantParam.ObjectId, grantParam.Grantee)
	if err != nil {
		commonHttpErr(fmt.Sprintf("get grant check data err %s", err.Error()), "获取数据失败", resp)
		return
	}
	// 存在授权记录，校验是否授权已经过期或者被撤销
	if grantCheckData != nil {
		if grantCheckData.Id == "" {
			commonHttpErr("grant id is nil", "授权数据查找出错", resp)
			return
		}
		if !grantIsRevoked(grantCheckData) && !grantIsOvertime(grantCheckData) {
			commonHttpErr("grant is exist", "授权已存在", resp)
			return
		}
		grantId = grantCheckData.Id
	}

	// 校验通过后, 查找被授权者的公钥
	grantedPubKey, ok, err := dc.srv.FindPubKeyByDid(grantParam.Grantee)
	if err != nil {
		commonHttpErr(fmt.Sprintf("find grantee pubkey error %s", err.Error()),
			"查找被授权者密钥失败", resp)
		return
	}
	if !ok {
		commonHttpErr(fmt.Sprintf("not find pub key of %s", grantParam.Grantee),
			"未找到授权者密钥", resp)
		return
	}
	var (
		rk, encryptRk string
	)
	// 生成对称密钥
	if rk = gm.GenerateSm4Key(); rk == "" {
		commonHttpErr(fmt.Sprintf("generate aes key error %s", err.Error()),
			"密钥生成失败", resp)
		return
	}
	// 使用被授权人的公钥对密钥加密
	if encryptRk = gm.EncryptByPubKey(grantedPubKey, []byte(rk)); encryptRk == "" {
		commonHttpErr(fmt.Sprintf("encrypt key error %s", err.Error()),
			"加密失败", resp)
		return
	}

	// 计算签名值
	signature, err := dc.signature(grantParam)
	if err != nil {
		commonHttpErr(fmt.Sprintf("calculate signatue err %s", err.Error()),
			"签名计算失败", resp)
		return
	}

	// 将所有数据存入表内
	if err = dc.srv.CreateCredentialGrant(grantId, walletId, &grantParam,
		rk, encryptRk, gm.SM4Name, signature); err != nil {
		commonHttpErr(fmt.Sprintf("insert credential grant error %s", err.Error()),
			"创建授权失败", resp)
		return
	}
	http.Ok(resp, "授权成功")
}

// CredentialGrantVerify 验证授权
func (dc *DidController) CredentialGrantVerify(req *restful.Request, resp *restful.Response) {
	var verifyParam param.CredentialGrantVerify
	if err := req.ReadEntity(&verifyParam); err != nil {
		commonHttpErr(fmt.Sprintf("read param err:%s", err.Error()),
			"参数解析失败", resp)
		return
	}
	if verifyParam.Verifier == "" || verifyParam.ObjectId == "" || verifyParam.Option == "" {
		commonHttpErr("param is nil", "非法参数", resp)
		return
	}
	// 查找授权记录
	grantData, ok := dc.srv.GetCredentialGrantVerify(verifyParam.ObjectId, verifyParam.Verifier)
	if !ok {
		commonHttpErr(fmt.Sprintf("not find grant %s of %s", verifyParam.ObjectId, verifyParam.Verifier),
			"未找到授权记录", resp)
		return
	}
	// 判断授权是否还在有效期
	if grantIsOvertime(grantData) {
		commonHttpErr(fmt.Sprintf("not in the grant duration, begin:%d, expired:%d",
			grantData.CreatedTime.Unix(), grantData.ExpirationDate.Unix()),
			"授权失效", resp)
		return
	}
	// 已经被撤销授权了验证不通过
	if grantIsRevoked(grantData) {
		commonHttpErr(fmt.Sprintf("grant already revoked, grant id %s", grantData.ObjectId),
			"授权已撤销", resp)
		return
	}
	// 判断是否赋予了对应操作权限
	if !matchCRUDPatten(grantData.Allow, verifyParam.Option) {
		commonHttpErr(fmt.Sprintf("opt permission not allow, allow:%s, opt:%s",
			grantData.Allow, verifyParam.Option), "未授予操作权限", resp)
		return
	}
	http.Ok(resp, "ok")
}

// CredentialGrantRevoke 授权撤销
func (dc *DidController) CredentialGrantRevoke(req *restful.Request, resp *restful.Response) {
	var (
		revokeParam param.CredentialGrantRevoke
		walletId    = auth.GetCurrentWalletId(req)
	)
	if err := req.ReadEntity(&revokeParam); err != nil {
		commonHttpErr(fmt.Sprintf("read param err:%s", err.Error()),
			"参数解析失败", resp)
		return
	}
	if revokeParam.GrantId == "" || revokeParam.Owner == "" {
		commonHttpErr("param has nil", "非法参数", resp)
		return
	}
	if !dc.srv.CheckDidOwnWallet(walletId, revokeParam.Owner) {
		commonHttpErr(fmt.Sprintf("wallet %s not have did %s", walletId, revokeParam.Owner),
			"钱包与did不一致", resp)
		return
	}
	// 校验收取按是否属于所有者
	if err := dc.checkGrantOwner(revokeParam.GrantId, revokeParam.Owner); err != nil {
		commonHttpErr(fmt.Sprintf("grant %s not is owner %s err %s",
			revokeParam.GrantId, revokeParam.Owner, err.Error()),
			err.Error(), resp)
		return
	}
	// 执行撤销授权操作
	if err := dc.srv.RevokeCredentialGrant(revokeParam.GrantId); err != nil {
		commonHttpErr(fmt.Sprintf("%s revoke grant %s fail %s", revokeParam.Owner,
			revokeParam.GrantId, err.Error()), "撤销授权失败", resp)
		return
	}
	http.Ok(resp, "ok")
}

func commonHttpErr(errMsg, httpMsg string, resp *restful.Response) {
	if errMsg != "" {
		log.Error(errMsg)
	}
	if resp != nil {
		http.Failed(resp, httpMsg)
	}
}

// grantParamValid 校验授权的参数是否合法
func (dc *DidController) grantParamValid(param param.CredentialGrant) bool {
	if param.ObjectId == "" || param.Grantee == "" {
		log.Error("objectId or grantee or owner or signature is nil")
		return false
	}
	// 校验一下开始时间和结束时间
	createTime, err := parseTime(param.CreateDate)
	if err != nil {
		log.Errorf("parse create time %s fail", param.CreateDate)
		return false
	}
	expiredTime, err := parseTime(param.Exp)
	if err != nil {
		log.Errorf("parse expired time %s fail", param.Exp)
		return false
	}
	if createTime.After(expiredTime) {
		log.Error(fmt.Sprintf("create time %s is after expired time %s",
			param.CreateDate, param.Exp))
		return false
	}

	// 检测ALLOW是不是仅包含CRUD
	if !checkCRUDPatten(param.Allow) {
		log.Errorf("allow is invalid %s", param.Allow)
		return false
	}
	return true
}

func (dc *DidController) signature(gp param.CredentialGrant) (string, error) {
	ownerPrvKey, ok, err := dc.srv.FindPrvKeyByDid(gp.Owner)
	if err != nil {
		return "", err
	}
	if !ok {
		return "", errors.New("not find owner prv key")
	}
	marshalParam, _ := json.Marshal(gp)
	sign := gm.Sign(ownerPrvKey, marshalParam)
	return sign, nil
}

// checkCRUDPatten 校验字符串是否仅包括CRUD四个字符，且不重复
func checkCRUDPatten(str string) bool {
	_, ok := makeCRUDPatten(str)
	return ok
}

// makeCRUDPatten 根据输入的字符串返回[C R U D]的索引标记数组，要求不可重复出现
// 例如：CRUD 返回[true true true true]，CD 返回[true false false true]
// 如果 CDD，CVD 则返回失败
func makeCRUDPatten(str string) ([]bool, bool) {
	if str == "" || len(str) > 4 {
		return nil, false
	}
	// go的正则不支持零宽度负向先行的断言先这样实现
	list := make([]bool, 4)
	for _, b := range []byte(str) {
		if index := getCRUDIndex(b); index >= 0 && !list[index] {
			list[index] = true
			continue
		}
		return nil, false
	}
	return list, true
}

// matchCRUDPatten patten是否符合match字符串的规则
// 1. 校验patten字符串中的每个字符都出现在match字符串中
// 2. 仅包括CRUD四个字符
func matchCRUDPatten(match, patten string) bool {
	if !checkCRUDPatten(patten) {
		return false
	}
	matchPatten, ok := makeCRUDPatten(match)
	if !ok {
		return false
	}
	for _, b := range []byte(patten) {
		if index := getCRUDIndex(b); index >= 0 && matchPatten[index] {
			continue
		}
		return false
	}
	return true
}

func getCRUDIndex(b byte) int {
	switch b {
	case 'C':
		return 0
	case 'R':
		return 1
	case 'U':
		return 2
	case 'D':
		return 3
	}
	return -1
}

// checkGrantOwner 校验凭证所有者是否一致
func (dc *DidController) checkGrantOwner(grantId, ownerDid string) error {
	isOwner, err := dc.srv.CheckGrantOwner(grantId, ownerDid)
	if err != nil {
		log.Errorf("check grant owner error: %s", err.Error())
		return errors.New("校验授权所有者出错")
	}
	if !isOwner {
		log.Errorf("grant %s owner %s is wrong",
			grantId, ownerDid)
		return errors.New("授权所有者不正确")
	}
	return nil
}

func grantIsRevoked(grant *entity.CredentialGrant) bool {
	if grant == nil {
		log.Panic("grant is nil")
	}
	return grant.Allow == ""
}

func grantIsOvertime(grant *entity.CredentialGrant) bool {
	if grant == nil {
		log.Panic("grant is nil")
	}
	now := time.Now()
	return !(now.After(grant.CreatedTime) && now.Before(grant.ExpirationDate))
}
